package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"github.com/apache/rocketmq-client-go/v2/producer"
	"github.com/opentracing/opentracing-go"
	"go.uber.org/zap"
	"golang.org/x/exp/rand"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"mxshop_srvs/order_srv/global"
	"mxshop_srvs/order_srv/model"
	"mxshop_srvs/order_srv/proto"
	"os"
	"time"
)

type OrderServer struct {
	proto.UnimplementedOrderServer
}

func GenerateOrderSn(userId int32) string {
	// 订单号的生成规则
	// 年月日时分秒+用户id+2位随机数
	now := time.Now()
	rand.Seed(uint64(time.Now().UnixNano()))
	orderSn := fmt.Sprintf("%d%d%d%d%d%d%d%d",
		now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Nanosecond(),
		userId, rand.Intn(100)+10)
	return orderSn
}

// 购物车
func (*OrderServer) CartItemList(ctx context.Context, req *proto.UserInfo) (*proto.CartItemListResponse, error) {
	//获取用户的购物车列表
	var shopCarts []model.ShoppingCart
	rsp := &proto.CartItemListResponse{}
	if result := global.DB.Where(&model.ShoppingCart{User: req.Id}).Find(&shopCarts); result.RowsAffected == 0 {
		return nil, result.Error
	} else {
		//设置总条数
		rsp.Total = int32(result.RowsAffected)
	}
	for _, shopCart := range shopCarts {
		rsp.Data = append(rsp.Data, &proto.ShopCartInfoResponse{
			Id:      shopCart.ID,
			UserId:  shopCart.User,
			GoodsId: shopCart.Goods,
			Nums:    shopCart.Nums,
			Checked: shopCart.Checked,
		})
	}
	return rsp, nil
}

func (*OrderServer) CreateCartItem(ctx context.Context, req *proto.CartItemRequest) (*proto.ShopCartInfoResponse, error) {
	//将商品添加到购物车, 1.购物车中原本没有这件商品 - 新建记录 2.这个商品之前添加到过购物车. - 合并数据
	var shopCart model.ShoppingCart

	if rs := global.DB.Where(&model.ShoppingCart{User: req.UserId, Goods: req.GoodsId}).Find(&shopCart); rs.RowsAffected == 1 {
		//记录存在则合并购物车记录 更新操作
		shopCart.Nums += req.Nums
	} else {
		//没有,则新建记录 插入操作
		shopCart.User = req.UserId
		shopCart.Goods = req.GoodsId
		shopCart.Nums = req.Nums
		shopCart.Checked = false
	}
	global.DB.Save(&shopCart)
	return &proto.ShopCartInfoResponse{Id: shopCart.ID}, nil
}

func (*OrderServer) UpdateCartItem(ctx context.Context, req *proto.CartItemRequest) (*emptypb.Empty, error) {
	var shopCart model.ShoppingCart
	//直接查询购物车商品对应的主键
	if rs := global.DB.First(&shopCart, req.Id); rs.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "购物车记录不存在")
	}
	shopCart.Checked = req.Checked
	if req.Nums > 0 {
		shopCart.Nums = req.Nums
	}
	global.DB.Save(&shopCart)
	return &emptypb.Empty{}, nil
}

func (*OrderServer) DeleteCartItem(ctx context.Context, req *proto.CartItemRequest) (*emptypb.Empty, error) {
	if rs := global.DB.Where("goods=? and user=?", req.GoodsId, req.UserId).Delete(&model.ShoppingCart{}, req.Id); rs.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "购物车记录不存在")
	}
	return &emptypb.Empty{}, nil
}

type OrderListener struct {
	Code        codes.Code
	Detail      string
	ID          int32
	OrderAmount float32
	Ctx         context.Context
}

// ExecuteLocalTransaction 本地执行逻辑无缘无故失败 代码异常 宕机 回查
func (o *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
	//解析json
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)
	parentSpan := opentracing.SpanFromContext(o.Ctx)
	var goodsIds []int32
	var shopCarts []model.ShoppingCart
	goodsNumsMap := make(map[int32]int32)
	shopCartSpan := opentracing.GlobalTracer().StartSpan("select_shopcart", opentracing.ChildOf(parentSpan.Context()))
	if rs := global.DB.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Find(&shopCarts); rs.RowsAffected == 0 {
		o.Code = codes.InvalidArgument
		o.Detail = "没有选中结算的商品"
		return primitive.RollbackMessageState
	}
	shopCartSpan.Finish()
	for _, shopCart := range shopCarts {
		goodsIds = append(goodsIds, shopCart.Goods)
		goodsNumsMap[shopCart.Goods] = shopCart.Nums
	}
	//跨服务调用 - 商品微服务
	queryGoodsSpan := opentracing.GlobalTracer().StartSpan("query_goods", opentracing.ChildOf(parentSpan.Context()))

	goods, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{Id: goodsIds})
	if err != nil {
		o.Code = codes.Internal
		o.Detail = "批量查询商品信息失败"
		return primitive.RollbackMessageState
	}
	queryGoodsSpan.Finish()
	var orderAmount float32
	var orderGoods []*model.OrderGoods
	var goodsInvInfo []*proto.GoodsInvInfo
	for _, good := range goods.Data {
		orderAmount += good.ShopPrice * float32(goodsNumsMap[good.Id]) //通过商品的id去查询商品的购买数量
		orderGoods = append(orderGoods, &model.OrderGoods{
			Goods:      good.Id,
			GoodsName:  good.Name,
			GoodsImage: good.GoodsFrontImage,
			GoodsPrice: good.ShopPrice,
			Nums:       goodsNumsMap[good.Id],
		})
		goodsInvInfo = append(goodsInvInfo, &proto.GoodsInvInfo{
			GoodsId: good.Id,
			Num:     goodsNumsMap[good.Id],
		})
	}
	//跨服务调用 - 库存微服务 - 库存扣减
	queryInvSpan := opentracing.GlobalTracer().StartSpan("query_inv", opentracing.ChildOf(parentSpan.Context()))
	if _, err := global.InventorySrvClient.Sell(context.Background(), &proto.SellInfo{GoodsInfo: goodsInvInfo}); err != nil {
		//1.如果是因为网络问题,这种如何避免误判,改写一下sell的返回逻辑
		o.Code = codes.ResourceExhausted
		o.Detail = "扣减库存失败"
		return primitive.RollbackMessageState
	}
	queryInvSpan.Finish()

	//生成订单表
	tx := global.DB.Begin()
	orderInfo.OrderMount = orderAmount
	saveOrderSpan := opentracing.GlobalTracer().StartSpan("save_order", opentracing.ChildOf(parentSpan.Context()))
	if rs := tx.Save(&orderInfo); rs.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "创建订单失败"
		return primitive.RollbackMessageState
	}
	saveOrderSpan.Finish()
	//外键
	for _, orderGood := range orderGoods {
		orderGood.Order = orderInfo.ID
	}
	//批量插入 orderGoods
	saveOrderGoodsSpan := opentracing.GlobalTracer().StartSpan("save_order_goods", opentracing.ChildOf(parentSpan.Context()))
	if rs := tx.CreateInBatches(orderGoods, 100); rs.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "批量插入订单商品失败"
		return primitive.CommitMessageState
	}
	saveOrderGoodsSpan.Finish()
	o.OrderAmount = orderAmount
	o.ID = orderInfo.ID
	deleteShopCartSpan := opentracing.GlobalTracer().StartSpan("delete_shop_cart", opentracing.ChildOf(parentSpan.Context()))
	if rs := tx.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Delete(&model.ShoppingCart{}); rs.RowsAffected == 0 {
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "删除购物车记录失败"
		return primitive.RollbackMessageState
	}
	deleteShopCartSpan.Finish()
	//发送延时消息
	p, _ := rocketmq.NewProducer(
		producer.WithNsResolver(primitive.NewPassthroughResolver([]string{fmt.Sprintf("%s:%d", global.ServerConfig.RocketMQInfo.Host, global.ServerConfig.RocketMQInfo.Port)})),
		producer.WithRetry(2),
	)
	err = p.Start()
	if err != nil {
		fmt.Printf("start producer error: %s", err.Error())
		os.Exit(1)
	}

	info := primitive.NewMessage("order_timeout", msg.Body)
	info.WithDelayTimeLevel(5)
	_, err = p.SendSync(context.Background(), info)
	if err != nil {
		zap.S().Errorf("发送延时消息失败:%v\n", err)
		tx.Rollback()
		o.Code = codes.Internal
		o.Detail = "发送延时消息失败"
		return primitive.CommitMessageState
	}
	//if err = p.Shutdown(); err != nil {
	//	fmt.Printf("shutdown producer error: %s", err.Error())
	//}
	tx.Commit()
	o.Code = codes.OK
	//本地执行逻辑无缘无故失败 代码异常 宕机
	return primitive.RollbackMessageState
}

// CheckLocalTransaction 消息回查
func (o *OrderListener) CheckLocalTransaction(msg *primitive.MessageExt) primitive.LocalTransactionState {
	var orderInfo model.OrderInfo
	_ = json.Unmarshal(msg.Body, &orderInfo)

	//检查之前的订单是否完成
	if rs := global.DB.Where(model.OrderInfo{OrderSn: orderInfo.OrderSn}).First(&orderInfo); rs.RowsAffected == 0 {
		return primitive.CommitMessageState //并不能说明库存已经扣减了
	}
	return primitive.RollbackMessageState
}

// CreateOrder 创建订单
func (*OrderServer) CreateOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	//新建订单
	// 1. 商品的金额价格 自行查询 - 访问商品服务(跨微服务)
	// 2. 库存扣减  - 访问库存服务(跨微服务)
	// 3. 订单基本信息表 - 订单的商品信息表
	// 4. 从购物车中获取到选中的商品
	// 5. 从购物车中删除已购买的记录
	orderListener := OrderListener{}
	p, err := rocketmq.NewTransactionProducer(
		&orderListener,
		producer.WithNameServer([]string{fmt.Sprintf("%s:%d", global.ServerConfig.RocketMQInfo.Host, global.ServerConfig.RocketMQInfo.Port)}), // 修改为实际可用的 NameServer 地址
		producer.WithRetry(2))
	if err != nil {
		zap.S().Errorf("生成producer失败:%s", err.Error())
		return nil, err
	}
	order := model.OrderInfo{
		OrderSn:      GenerateOrderSn(req.UserId),
		Address:      req.Address,
		SignerName:   req.Name,
		SingerMobile: req.Mobile,
		Post:         req.Post,
		User:         req.UserId,
	}
	//具体指明一个订单具体商品的扣减情况
	jsonStr, _ := json.Marshal(order)

	if err = p.Start(); err != nil {
		zap.S().Errorf("启动producer失败:%s", err.Error())
		return nil, err
	}
	_, err = p.SendMessageInTransaction(context.Background(), primitive.NewMessage("order_reback", jsonStr))
	if err != nil {
		fmt.Printf("发送失败:%s\n", err)
		return nil, status.Error(codes.Internal, "发送消息失败")
	}
	if orderListener.Code != codes.OK {
		return nil, status.Error(orderListener.Code, orderListener.Detail)
	}

	return &proto.OrderInfoResponse{Id: orderListener.ID, OrderSn: order.OrderSn, Total: orderListener.OrderAmount}, nil
}

// OrderList 订单列表接口
func (*OrderServer) OrderList(ctx context.Context, req *proto.OrderFilterRequest) (*proto.OrderListResponse, error) {
	var orders []model.OrderInfo
	var rsp proto.OrderListResponse

	var total int64
	//这种查询写法 gorm 会忽略 零值
	global.DB.Where(&model.OrderInfo{User: req.UserId}).Count(&total)
	rsp.Total = int32(total)

	//分页
	global.DB.Scopes(Paginate(int(req.Pages), int(req.PagePerNums))).Find(&orders)
	for _, order := range orders {
		rsp.Data = append(rsp.Data, &proto.OrderInfoResponse{
			Id:      order.ID,
			UserId:  order.User,
			OrderSn: order.OrderSn,
			PayType: order.PayType,
			Status:  order.Status,
			Post:    order.Post,
			Total:   order.OrderMount,
			Address: order.Address,
			Name:    order.SignerName,
			Mobile:  order.SingerMobile,
		})
	}
	return &rsp, nil
}
func (*OrderServer) OrderDetail(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoDetailResponse, error) {
	var order model.OrderInfo
	var rsp proto.OrderInfoDetailResponse
	//订单id 是否是当前用户的订单,如果在web层用户传递过来的id 的订单,web层应该先查询一下订单id是否是当前用户的id
	//在个人中心可以这样做,如果是后台管理系统,web 如果是后台管理系统,那么只传递order 的 id ,用户的话需要多加一个用户id
	if result := global.DB.Where(
		&model.OrderInfo{
			BaseModel: model.BaseModel{
				ID: req.Id},
			User: req.UserId,
		}).First(&order); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在")
	}
	//订单基本信息
	rsp.OrderInfo.Id = order.ID
	rsp.OrderInfo.UserId = order.User
	rsp.OrderInfo.OrderSn = order.OrderSn
	rsp.OrderInfo.PayType = order.PayType
	rsp.OrderInfo.Status = order.Status
	rsp.OrderInfo.Post = order.Post
	rsp.OrderInfo.Total = order.OrderMount
	rsp.OrderInfo.Address = order.Address
	rsp.OrderInfo.Name = order.SignerName
	rsp.OrderInfo.Mobile = order.SingerMobile

	var orderGoods []model.OrderGoods
	if rs := global.DB.Where(&model.OrderGoods{Order: order.ID}).Find(&orderGoods); rs.Error != nil {
		return nil, rs.Error
	}
	//订单下的所有商品
	for _, orderGood := range orderGoods {
		rsp.Goods = append(rsp.Goods, &proto.OrderItemResponse{
			GoodsId:    orderGood.Goods,
			GoodsName:  order.SignerName,
			GoodsPrice: orderGood.GoodsPrice,
			Nums:       orderGood.Nums,
		})
	}

	return &rsp, nil
}
func (*OrderServer) UpdateOrderStatus(ctx context.Context, req *proto.OrderStatus) (*emptypb.Empty, error) {
	//先查询,再更新 实际上有两条sql 执行，select 和 update 语句
	if rs := global.DB.Model(&model.OrderInfo{}).Where("order_sn", req.OrderSn).Update("status", req.Status); rs.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "订单不存在")
	}
	return &emptypb.Empty{}, nil
}

// TryCreateOrder 创建订单
/**
  TCC分布式事务 单个inventory服务为例
*/
func (*OrderServer) TryCreateOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
	//新建订单
	// 1. 商品的金额价格 自行查询 - 访问商品服务(跨微服务)
	// 2. 库存扣减  - 访问库存服务(跨微服务)
	// 3. 订单基本信息表 - 订单的商品信息表
	// 4. 从购物车中获取到选中的商品
	// 5. 从购物车中删除已购买的记录
	var goodsIds []int32
	var shopCarts []model.ShoppingCart
	goodsNumsMap := make(map[int32]int32)
	if rs := global.DB.Where(&model.ShoppingCart{User: req.UserId, Checked: true}).Find(&shopCarts); rs.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "没有选中结算的商品")
	}
	for _, shopCart := range shopCarts {
		goodsIds = append(goodsIds, shopCart.Goods)
		goodsNumsMap[shopCart.Goods] = shopCart.Nums
	}
	//跨服务调用 - 商品微服务
	goods, err := global.GoodsSrvClient.BatchGetGoods(context.Background(), &proto.BatchGoodsIdInfo{Id: goodsIds})
	if err != nil {
		return nil, status.Errorf(codes.Internal, "批量查询商品信息失败")
	}
	var orderAmount float32
	var orderGoods []*model.OrderGoods
	var goodsInvInfo []*proto.GoodsInvInfo
	for _, good := range goods.Data {
		orderAmount += good.ShopPrice * float32(goodsNumsMap[good.Id]) //通过商品的id去查询商品的购买数量
		orderGoods = append(orderGoods, &model.OrderGoods{
			Goods:      good.Id,
			GoodsName:  good.Name,
			GoodsImage: good.GoodsFrontImage,
			GoodsPrice: good.ShopPrice,
			Nums:       goodsNumsMap[good.Id],
		})
		goodsInvInfo = append(goodsInvInfo, &proto.GoodsInvInfo{
			GoodsId: good.Id,
			Num:     goodsNumsMap[good.Id],
		})
	}
	//跨服务调用 - 库存微服务 - 库存扣减
	/**
	  	 1.调用库服务的trySell
	  	 2.调用仓库服务的trySell
	  	 3.调用积分服务的tryAdd
	  任何一个出现异常,那么你得调用对应的所有微服务的 `cancel` 接口
	  如果所有的微服务都正常,那么你得调用对应的所有微服务的 `confirm` 接口
	*/
	if _, err := global.InventorySrvClient.Sell(context.Background(), &proto.SellInfo{GoodsInfo: goodsInvInfo}); err != nil {
		return nil, status.Errorf(codes.ResourceExhausted, "扣减库存失败")
	}

	//生成订单表
	tx := global.DB.Begin()
	order := model.OrderInfo{
		OrderSn:      GenerateOrderSn(req.UserId),
		OrderMount:   orderAmount,
		Address:      req.Address,
		SignerName:   req.Name,
		SingerMobile: req.Mobile,
		Post:         req.Post,
	}
	if rs := tx.Save(&order); rs.RowsAffected == 0 {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "创建订单失败")
	}
	//外键
	for _, orderGood := range orderGoods {
		orderGood.Order = order.ID
	}
	//批量插入 orderGoods
	if rs := tx.CreateInBatches(orderGoods, 100); rs.RowsAffected == 0 {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "创建订单失败")
	}

	if rs := tx.Where(&model.ShoppingCart{User: req.UserId, Checked: true}).Delete(&model.ShoppingCart{}); rs.RowsAffected == 0 {
		//在这里调用库存服务的reback接口,去归还之前扣减库存就行了
		//global.InventorySrvClient.ReBack() //出现由于磁盘满了 导致服务宕机 不能归还  网络出问题了
		//重试 - 但是网络问题也比较复杂,抖动或拥塞 , 之前发送过的调用过一段时间之后才会被接受到 - 会不会重复归还 - 幂等性
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "创建订单失败")
	}
	tx.Commit()
	return &proto.OrderInfoResponse{Id: order.ID, OrderSn: order.OrderSn, Total: order.OrderMount}, nil
}

func OrderTimeOut(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
	for _, msg := range msgs {
		var orderInfo model.OrderInfo
		_ = json.Unmarshal(msg.Body, &orderInfo)
		//查询订单的支付状态,如果已支付就什么都不做,如果未支付,归还库存
		var order model.OrderInfo
		if rs := global.DB.Model(model.OrderInfo{}).Where(model.OrderInfo{OrderSn: orderInfo.OrderSn}).First(&order); rs.RowsAffected == 0 {
			return consumer.ConsumeSuccess, nil
		}
		if order.Status != "TRADER_SUCCESS" {
			tx := global.DB.Begin()
			//修改订单的状态 为 已支付
			order.Status = "TRADER_CLOSED"
			tx.Save(&order)
			//归还库存 可以模仿order中发送一个消息到 order_reback中去
			//1. 初始化发送者
			p, err := rocketmq.NewProducer(
				producer.WithNsResolver(primitive.NewPassthroughResolver([]string{fmt.Sprintf("%s:%d", global.ServerConfig.RocketMQInfo.Host, global.ServerConfig.RocketMQInfo.Port)})),
				producer.WithRetry(2))
			if err != nil {
				panic("生成Producer失败")
			}
			if err = p.Start(); err != nil {
				panic("启动 Producer 失败")
			}
			//2. 发送消息
			if res, err := p.SendSync(context.Background(), primitive.NewMessage(" order_reback", msg.Body)); err != nil {
				tx.Rollback()
				fmt.Printf("发送失败:%s\n", err)
				return consumer.ConsumeRetryLater, nil
			} else {
				fmt.Printf("发送成功:%s\n", res.String())
			}
			////3.关闭消息发送者
			//if err = p.Shutdown(); err != nil {
			//	panic("关闭 Producer 失败")
			//}
		}
	}
	return consumer.ConsumeSuccess, nil
}
